import * as THREE from 'three';
import { Camera,ShaderMaterial, ColorRepresentation, Euler, Fog, FogExp2, Group, Mesh, Object3D, OrthographicCamera, PerspectiveCamera, PMREMGenerator, SkeletonHelper, Texture, Vector2, Vector3, WebGLRenderer, Color } from 'three';
import {TransformControls} from "./controls/TransformControls.js";
import { Editor, EditorTheme } from './editor.js';
import {EditorControls} from './controls.js';
import { ViewHelper } from './viewhelper.js';
import { TezeroOptions } from '../index';
import {TezeroMode} from './enums.js';
import { SetPositionCommand, SetRotationCommand, SetScaleCommand } from './commands/Commands.js';
import Stats from 'three/examples/jsm/libs/stats.module';
import {CSS2DObject,CSS2DRenderer} from 'three/examples/jsm/renderers/CSS2DRenderer.js';
import {CSS3DObject,CSS3DSprite,CSS3DRenderer} from 'three/examples/jsm/renderers/CSS3DRenderer.js';

const MATERIAL_NORMAL = new THREE.MeshNormalMaterial( {
	// displacementMap: displacementMap,
	// displacementScale: SCALE,
	// displacementBias: BIAS,

	// normalMap: normalMap,
	normalScale: new THREE.Vector2( 1, - 1 ),

	flatShading: true,

	side: THREE.DoubleSide
} );

class Viewport{
	editor:Editor;
	dom:HTMLElement;
	camera:Camera;

	viewHelper:ViewHelper;
	controls:EditorControls;

	// object picking

	raycaster = new THREE.Raycaster();
	mouse = new THREE.Vector2();

	
	onDownPosition = new THREE.Vector2();
	onUpPosition = new THREE.Vector2();
	onDoubleClickPosition = new THREE.Vector2();


	clock = new THREE.Clock(); // only used for animations

	//

	startTime = 0;
	endTime = 0;

	objects:Object3D[]=[];

	grid?:Group;
	renderer?:WebGLRenderer;
	css2dRenderer?:CSS2DRenderer;
	css3dRenderer?:CSS3DRenderer;

	mode:TezeroMode=TezeroMode.Edit;

	showSceneHelpers = true;

	options:TezeroOptions;

	stats:Stats;
	private _fps:number = 60;
	private _timeStamp:number=0;
	private _singleFrameTime:number=(1/this._fps);
	set fps(fps:number){
		this._fps=fps;
		this._singleFrameTime=1/fps;
	}
	constructor( editor:Editor,options:TezeroOptions ) {
		this.editor=editor;
		this.options=options;
		const signals = editor.signals;

		this.dom=document.createElement('div');
		this.dom.className="tezero-viewport";
		this.dom.style.position='relative';
		this.dom.style.width='100%';
		this.dom.style.height='100%';
		this.dom.style.outline='none';
		this.dom.setAttribute('tabindex','0');

		this.stats=new Stats();
		this.stats.dom.style.position='absolute';
		this.stats.dom.style.top='0';
		this.stats.dom.style.left='0';
		this.stats.dom.style.zIndex='auto';
		this.stats.dom.style.display='none';
    

		//
		var pmremGenerator:PMREMGenerator;

		var camera = editor.camera;
		this.camera=camera;
		var scene = editor.scene;
		var sceneHelpers = editor.sceneHelpers;

		this.viewHelper = new ViewHelper( camera, this.dom );

		// helpers
		var grid = new THREE.Group();

		var grid1 = new THREE.GridHelper( 30, 30, 0xededee );//0x888888
		grid1.material.color.setHex( 0xededee );
		grid1.material.vertexColors = false;
		grid.add( grid1 );

		var grid2 = new THREE.GridHelper( 30, 6, 0xededee );//0x222222
		grid2.material.color.setHex( 0xededee );
		grid2.material.depthFunc = THREE.AlwaysDepth;
		grid2.material.vertexColors = false;
		grid.add( grid2 );

		this.grid=grid;

		//

		var box = new THREE.Box3();

		var selectionBox = new THREE.BoxHelper(new Object3D());
		selectionBox.material.depthTest = false;
		selectionBox.material.transparent = true;
		if(this.options?.selectColor){
			selectionBox.material.color=new THREE.Color(this.options?.selectColor);
		}
		selectionBox.visible = false;
		sceneHelpers.add( selectionBox );

		var objectPositionOnDown:Vector3|undefined;
		var objectRotationOnDown:Euler|undefined;
		var objectScaleOnDown:Vector3|undefined;

		
		// 多选组
		var selectionGroup = new THREE.Group();
		sceneHelpers.add( selectionGroup );


		var transformControls = new TransformControls( camera, this.dom );
		transformControls.addEventListener( 'change',  () =>{

			var object = transformControls.object;

			if ( object !== undefined ) {

				selectionBox.setFromObject( object );

				var helper = editor.helpers[ object.id ];

				if ( helper !== undefined && helper instanceof SkeletonHelper) {

					helper.update();

				}

				signals.refreshSidebarObject3D.dispatch( object );

			}

			this.render();

		} );
		transformControls.addEventListener( 'mouseDown',  () =>{

			var object = transformControls.object;

			objectPositionOnDown = object?.position.clone();
			objectRotationOnDown = object?.rotation.clone();
			objectScaleOnDown = object?.scale.clone();

			this.controls.enabled = false;

		} );
		transformControls.addEventListener( 'mouseUp',  () =>{

			var object = transformControls.object;

			if ( object !== undefined ) {

				switch ( transformControls.getMode() ) {

					case 'translate':

						if ( ! objectPositionOnDown?.equals( object.position ) ) {

							editor.execute( new SetPositionCommand( editor, object, object.position, objectPositionOnDown ) );

						}

						break;

					case 'rotate':

						if ( ! objectRotationOnDown?.equals( object.rotation ) ) {

							editor.execute( new SetRotationCommand( editor, object, object.rotation, objectRotationOnDown ) );

						}

						break;

					case 'scale':

						if ( ! objectScaleOnDown?.equals( object.scale ) ) {

							editor.execute( new SetScaleCommand( editor, object, object.scale, objectScaleOnDown ) );

						}

						break;

				}

			}

			this.controls.enabled = true;

		} );

		sceneHelpers.add( transformControls );
		

		// events

		this.dom.addEventListener( 'mousedown', this.onMouseDown.bind(this), false );
		this.dom.addEventListener( 'touchstart', this.onTouchStart.bind(this), false );
		this.dom.addEventListener( 'dblclick', this.onDoubleClick.bind(this), false );

		// controls need to be added *after* main logic,
		// otherwise controls.enabled doesn't work.

		this.controls = new EditorControls( camera, this.dom );
		if(this.options.control){
			this.controls.enabled=this.options.control.enabled;
			this.controls.zoomSpeed=this.options.control.zoomSpeed;
			this.controls.panSpeed=this.options.control.panSpeed;
			this.controls.rotationSpeed=this.options.control.rotationSpeed;
		}
		this.controls.addEventListener( 'change',  ()=>{

			signals.cameraChanged.dispatch( camera );
			signals.refreshSidebarObject3D.dispatch( camera );

		} );
		this.viewHelper.center = this.controls.center;
		if(this.mode!==TezeroMode.Edit){
			this.viewHelper.destroy();
		}

		// signals

		signals.editorCleared.add( ()=> {

			if(this.css3dRenderer){
				this.css3dRenderer.domElement.firstChild.firstChild.childNodes.forEach(element => {
					element.remove();
				});
			}

			this.controls.center.set( 0, 0, 0 );
			this.render(true);

		} );

		signals.transformModeChanged.add(  ( mode:"translate" | "rotate" | "scale" )=> {

			transformControls.setMode( mode );
			this.render(true);

		} );

		signals.snapChanged.add(  ( dist:number|null )=> {

			transformControls.setTranslationSnap( dist );

		} );

		signals.spaceChanged.add( ( space:"world" | "local" ) =>{

			transformControls.setSpace( space );

		} );

		signals.rendererUpdated.add(  (animate?:boolean) =>{
			if(animate===true){
				if(this.viewHelper&&this.mode!==TezeroMode.Edit){
					this.viewHelper.destroy();
				}else{
					this.viewHelper=new ViewHelper(this.camera,this.dom);
				}
				this.renderer?.setAnimationLoop( this.animate.bind(this) );
			}
			
			scene.traverse( function ( child ) {

				if ( child instanceof Mesh && child.material !== undefined ) {

					child.material.needsUpdate = true;

				}

			} );

			this.render();

		} );

		signals.rendererCreated.add( ( newRenderer:WebGLRenderer ) =>{

			if ( this.renderer ) {

				this.renderer.setAnimationLoop( null );
				this.renderer.dispose();
				pmremGenerator.dispose();

				this.dom.removeChild( this.renderer.domElement );

			}
			if(this.css3dRenderer){
				this.dom.removeChild( this.css3dRenderer.domElement )
			}

			this.renderer = newRenderer;
			

			this.renderer?.setAnimationLoop( this.animate.bind(this) );
			
			if ( window.matchMedia&&this.editor.theme===EditorTheme.System ) {

				var mediaQuery = window.matchMedia( '(prefers-color-scheme: dark)' );
				mediaQuery.addListener(  ( event ) =>{

					this.renderer?.setClearColor( event.matches ? 0x333333 : 0xd2d3d6 );//0xaaaaaa
					
					this.updateGridColors( this.grid.children[0] as Mesh, this.grid.children[1] as Mesh, event.matches ? [ 0x222222, 0x888888 ] : [ 0x888888, 0x282828 ] );

					this.render();

				} );

				this.renderer?.setClearColor( mediaQuery.matches ? 0x333333 : 0xd2d3d6 );
				this.updateGridColors(this.grid.children[0] as Mesh, this.grid.children[1] as Mesh, mediaQuery.matches ? [ 0x222222, 0x888888 ] : [ 0x888888, 0x282828 ] );

			}else if(this.editor.theme===EditorTheme.Dark){
				this.renderer?.setClearColor( 0x333333 );
				this.updateGridColors(this.grid.children[0] as Mesh, this.grid.children[1] as Mesh, [ 0x222222, 0x888888 ] );
			}else{
				this.renderer?.setClearColor( 0xd2d3d6 );
				this.updateGridColors(this.grid.children[0] as Mesh, this.grid.children[1] as Mesh, [ 0x888888, 0x282828 ] );
			}

			this.renderer?.setPixelRatio( window.devicePixelRatio );
			this.renderer?.setSize( this.dom.offsetWidth, this.dom.offsetHeight );
			// this.renderer=renderer;

			if(this.renderer){
				pmremGenerator = new THREE.PMREMGenerator( this.renderer );
				pmremGenerator.compileEquirectangularShader();

				this.dom.appendChild( this.renderer.domElement );
			}
			
			const target=new THREE.Vector2();
			const size=newRenderer.getSize(target);
			this.css3dRenderer=new CSS3DRenderer();
			this.css3dRenderer.setSize(size.width,size.height);
			this.css3dRenderer.domElement.style.position="absolute";
			this.css3dRenderer.domElement.style.top='0';
			this.css3dRenderer.domElement.style.left='0';
			this.css3dRenderer.domElement.className = 'tezero-css3d-container';
			this.css3dRenderer.domElement.style.pointerEvents='none';
			this.dom.appendChild(this.css3dRenderer.domElement);

			this.render(true);

		} );

		signals.sceneGraphChanged.add(  (force?:boolean)=> {

			this.render(force);

		} );



		signals.cameraChanged.add(  ()=> {

			this.render();

		} );

		signals.objectSelected.add(  ( object:Object3D|null ) =>{

			selectionBox.visible = false;
			transformControls.detach();

			if ( object !== null && object !== scene && object !== camera && this.editor.selectedObjects.length===0) {

				box.setFromObject( object );

				if ( box.isEmpty() === false ) {

					selectionBox.setFromObject( object );
					selectionBox.visible = true;

				}

				transformControls.attach( object );

			}else if(this.editor.selectedObjects.find(o=>o.uuid==scene.uuid)==null||this.editor.selectedObjects.find(o=>o.uuid==camera.uuid)==null){
				
				selectionGroup.children.forEach(obj=>{

					selectionGroup.remove(obj);
				});
				  
				// 计算组的中心位置
				const center = new THREE.Vector3();
				this.editor.selectedObjects.forEach(obj => {
					center.add(obj.position);
				});
				center.divideScalar(this.editor.selectedObjects.length);
				selectionGroup.position.copy(center);
				
				// 添加对象到组，并调整它们的相对位置
				this.editor.selectedObjects.forEach(obj => {
					const relativePos = obj.position.clone().sub(center);
					// this.scene.attach(obj); // 先从父级移除
					// 隐藏源网格
					obj.userData.selectable = false;
					obj.visible=false;
					const selectionObj=obj.clone(true);
					selectionObj.visible=true;
					selectionGroup!.add(selectionObj);
					selectionObj.position.copy(relativePos);
				});
				box.setFromObject( selectionGroup );
				if ( box.isEmpty() === false ) {

					selectionBox.setFromObject( selectionGroup );
					selectionBox.visible = true;

				}

				transformControls.attach( selectionGroup );
			}

			this.render(true);

		} );

		signals.objectFocused.add(  ( object:Object3D|null)=> {
			if(object){
				this.controls.focus( object );
			}
			

		} );

		signals.geometryChanged.add(  ( object?:Object3D ) =>{

			if ( object !== undefined ) {

				selectionBox.setFromObject( object );

			}

			this.render();

		} );

		signals.objectAdded.add(  ( object:Object3D )=> {

			object.traverse(  ( child ) =>{

				this.objects.push( child );

			} );

		} );

		signals.objectChanged.add(  ( object:Object3D ) =>{

			if ( editor.selected === object ) {

				selectionBox.setFromObject( object );

			}

			if ( object instanceof PerspectiveCamera) {

				object.updateProjectionMatrix();

			}

			if ( editor.helpers[ object.id ] !== undefined ) {

				(editor.helpers[ object.id ] as SkeletonHelper)?.update();

			}

			this.render(true);

		} );

		signals.objectRemoved.add(  ( object:Object3D ) =>{

			this.controls.enabled = true; // see #14180
			if ( object === transformControls.object ) {

				transformControls.detach();

			}

			object.traverse(  ( child )=> {

				this.objects.splice( this.objects.indexOf( child ), 1 );

			} );

		} );

		signals.helperAdded.add(  ( object:Object3D ) =>{

			var picker = object.getObjectByName( 'picker' );

			if ( picker !== undefined ) {

				this.objects.push( picker );

			}

		} );

		signals.helperRemoved.add(  ( object:Object3D ) =>{

			var picker = object.getObjectByName( 'picker' );

			if ( picker !== undefined ) {

				this.objects.splice( this.objects.indexOf( picker ), 1 );

			}

		} );

		signals.materialChanged.add(  ()=> {

			this.render(true);

		} );

		// signals.animationStopped.add( function () {

		// 	this.render();

		// } );

		// background

		//signals.sceneBackgroundChanged.add( ( backgroundType:"None"|"Color"|"Texture"|"Equirectangular", backgroundColor:ColorRepresentation , backgroundTexture:Texture , backgroundEquirectangularTexture:Texture )=> {
		signals.sceneBackgroundChanged.add( (background:null|Color|Texture )=> {
			//THREE.EquirectangularReflectionMapping
			scene.background=background;

			this.render();

		} );

		// environment

		// signals.sceneEnvironmentChanged.add(  ( environmentType:"None"|"Equirectangular"|"ModelViewer", environmentEquirectangularTexture:Texture )=> {
		signals.sceneEnvironmentChanged.add(  ( environmentEquirectangularTexture:Texture|null )=> {

			

			scene.environment = null;

			if ( environmentEquirectangularTexture ) {

				scene.environment = pmremGenerator.fromEquirectangular( environmentEquirectangularTexture ).texture;

			}

			this.render();

		} );

		// fog

		signals.sceneFogChanged.add(  ( fogType:"None"|"Fog"|"FogExp2", fogColor:ColorRepresentation, fogNear:number | undefined, fogFar:number | undefined, fogDensity:number | undefined )=>{

			switch ( fogType ) {

				case 'None':
					scene.fog = null;
					break;
				case 'Fog':
					scene.fog = new THREE.Fog( fogColor, fogNear, fogFar );
					break;
				case 'FogExp2':
					scene.fog = new THREE.FogExp2( fogColor, fogDensity );
					break;

			}

			this.render();

		} );

		signals.sceneFogSettingsChanged.add(  ( fogType:"None"|"Fog"|"FogExp2", fogColor:number, fogNear:number, fogFar:number, fogDensity:number )=> {
			if(scene.fog){
				if(scene.fog instanceof Fog){
					scene.fog.color.setHex( fogColor );
					scene.fog.near = fogNear;
					scene.fog.far = fogFar;
				}else if(scene.fog instanceof FogExp2){
					scene.fog.color.setHex( fogColor );
					scene.fog.density = fogDensity;
				}
			}
			

			this.render();

		} );

		signals.viewportCameraChanged.add(  () =>{

			var viewportCamera = editor.viewportCamera;

			if ( viewportCamera instanceof PerspectiveCamera) {

				viewportCamera.aspect = (editor.camera as PerspectiveCamera).aspect;
				viewportCamera.projectionMatrix.copy( editor.camera.projectionMatrix );

			} else if ( viewportCamera instanceof OrthographicCamera) {

				// TODO

			}

			// disable EditorControls when setting a user camera
			const enabled=( viewportCamera === editor.camera );
			this.controls.enabled =enabled;
			this.showSceneHelpers=enabled;

			this.render(true);

		} );

		signals.exitedVR.add( this.render );

		//

		signals.windowResize.add( ()=>{

			this.updateAspectRatio();

			this.renderer?.setSize( this.dom.offsetWidth, this.dom.offsetHeight );
			this.css3dRenderer?.setSize( this.dom.offsetWidth, this.dom.offsetHeight );

			this.render();

		} );

		signals.showGridChanged.add(  ( showGrid:boolean )=> {

			grid.visible = showGrid;
			this.render();

		} );

		signals.showHelpersChanged.add(  ( showHelpers:boolean ) =>{

			this.showSceneHelpers = showHelpers;
			transformControls.enabled = showHelpers;

			this.render();

		} );

		signals.cameraResetted.add( this.updateAspectRatio.bind(this) );
		
		signals.viewportShadingChanged.add(()=>{
			this.editor.scene.traverse((child)=>{

					if (child instanceof Mesh) {
						if(this.editor.viewportShading==='wireframe'){
							if(child.userData._normals){
								child.material=child.userData._normals;
							}
							child.material.wireframe = true;
						}else if(this.editor.viewportShading==='normals'){
							child.material.wireframe = false;
							child.userData._normals=child.material;
							child.material=MATERIAL_NORMAL.clone();
						}else{
							child.material.wireframe = false;
							if(child.userData._normals){
								child.material=child.userData._normals;
							}
						}

					}
			});
			this.render(true);
		});

	}

		

	updateAspectRatio() {
		// this.camera=this.editor.camera;
		if(this.camera!==undefined&&this.camera instanceof PerspectiveCamera){
			this.camera.aspect = this.dom.offsetWidth / this.dom.offsetHeight;
			this.camera.updateProjectionMatrix();
		}
		

	}

	getIntersects( point:Vector2, objects:Object3D[] ) {

		this.mouse.set( ( point.x * 2 ) - 1, - ( point.y * 2 ) + 1 );

		this.raycaster.setFromCamera( this.mouse, this.camera );

		return this.raycaster.intersectObjects( objects );

	}

	getMousePosition( dom:HTMLElement, x:number, y:number ) {

		var rect = dom.getBoundingClientRect();
		return [ ( x - rect.left ) / rect.width, ( y - rect.top ) / rect.height ];

	}

	handleClick(event:MouseEvent|TouchEvent) {

		if ( this.onDownPosition.distanceTo( this.onUpPosition ) === 0 ) {
			this.onDownPosition=new Vector2();

			const modKey=event.ctrlKey||event.metaKey;

			var intersects = this.getIntersects( this.onUpPosition, this.objects );

			if ( intersects.length > 0 ) {

				var object = intersects[ 0 ].object;

				if ( object.userData.object !== undefined ) {

					// helper
					if(modKey){
						this.editor.selects( object.userData.object );
					}else{
						this.editor.select( object.userData.object );
					}
					

				} else {
					if(modKey){
						this.editor.selects( object );
					}else{
						this.editor.select( object );
					}

				}

			} else if(!modKey){

				this.editor.select(  );

			}

			this.render(true);

		}

	}

	onMouseDown( event:MouseEvent ) {
		event.stopPropagation();
		event.preventDefault();

		var array = this.getMousePosition( this.dom, event.clientX, event.clientY );
		this.onDownPosition.fromArray( array );

		document.addEventListener( 'mouseup', this.onMouseUp.bind(this), false );

	}

	onMouseUp( event:MouseEvent ) {
		event.stopPropagation();

		var array = this.getMousePosition( this.dom, event.clientX, event.clientY );
		this.onUpPosition.fromArray( array );

		this.handleClick(event);

		document.removeEventListener( 'mouseup', this.onMouseUp.bind(this), false );

	}

	onTouchStart( event:TouchEvent ) {
		event.stopPropagation();
		var touch = event.changedTouches[ 0 ];

		var array = this.getMousePosition( this.dom, touch.clientX, touch.clientY );
		this.onDownPosition.fromArray( array );

		document.addEventListener( 'touchend', this.onTouchEnd.bind(this), false );

	}

	onTouchEnd( event:TouchEvent ) {

		var touch = event.changedTouches[ 0 ];

		var array = this.getMousePosition( this.dom, touch.clientX, touch.clientY );
		this.onUpPosition.fromArray( array );

		this.handleClick(event);

		document.removeEventListener( 'touchend', this.onTouchEnd.bind(this), false );

	}

	onDoubleClick( event:MouseEvent ) {

		var array = this.getMousePosition( this.dom, event.clientX, event.clientY );
		this.onDoubleClickPosition.fromArray( array );

		var intersects = this.getIntersects( this.onDoubleClickPosition, this.objects );

		if ( intersects.length > 0 ) {

			var intersect = intersects[ 0 ];

			this.editor.signals.objectFocused.dispatch( intersect.object );

		}

	}

	// animations
	animate() {

		var mixer = this.editor.mixer;
		var delta = this.clock.getDelta();

		var needsUpdate = false;

		mixer.update( delta );

		this.stats.update();

		if ( this.mode===TezeroMode.Edit && this.viewHelper.animating === true ) {

			this.viewHelper.update( delta );
			needsUpdate = true;

		}

		// if ( vr.currentSession !== null ) {

		// 	needsUpdate = true;

		// }
		
		if ( needsUpdate === true ){
			
				this.render();//渲染
			
		} 

	}

	render(force?:boolean) {
		if(force!==true){
			var delta = this.clock.getDelta();
			this._timeStamp += delta;
			if (this._timeStamp <= this._singleFrameTime) {
				return;
			}
			this._timeStamp = (this._timeStamp % this._singleFrameTime);
		}
		

		this.startTime = performance.now();

		// Adding/removing grid to scene so materials with depthWrite false
		// don't render under the grid.
		if(this.grid){
			this.editor.scene.add( this.grid );
		}
		this.renderer?.setViewport( 0, 0, this.dom.offsetWidth, this.dom.offsetHeight );
		this.renderer?.render( this.editor.scene, this.editor.viewportCamera );
		this.css3dRenderer?.render(this.editor.scene, this.editor.viewportCamera );
		if(this.grid){
			this.editor.scene.remove( this.grid );
		}

		if ( this.camera === this.editor.viewportCamera && this.renderer) {

			this.renderer.autoClear = false;
			if ( this.showSceneHelpers === true ) this.renderer.render( this.editor.sceneHelpers, this.camera );
			if(this.mode===TezeroMode.Edit){
				this.viewHelper.render( this.renderer );
			}
			this.renderer.autoClear = true;

		}

		this.endTime = performance.now();
		this.editor.signals.sceneRendered.dispatch( this.endTime - this.startTime );

	}

	updateGridColors( grid1:Mesh, grid2:Mesh, colors:number[] ) {
		const material=grid1.material;
		if(material&&material instanceof THREE.MeshBasicMaterial){	
			material.color.setHex( colors[ 0 ] );
			material.color.setHex( colors[ 1 ] );
		}
		if(grid2.material&&grid2.material instanceof THREE.MeshBasicMaterial){	
			grid2.material.color.setHex( colors[ 0 ] );
			grid2.material.color.setHex( colors[ 1 ] );
		}

	}

}


export { Viewport };
